<?php
/**
 * Created by PhpStorm.
 * User: whwyy
 * Date: 2018/10/7 0007
 * Time: 2:13
 */

namespace BeReborn\Base;


use BeReborn\Annotation\Annotation;
use BeReborn\Cache\Redis;
use BeReborn\Di\Service;
use BeReborn\Error\ErrorHandler;
use BeReborn\Error\RestfulHandler;
use BeReborn\Event\Event;
use BeReborn\Exception\ComponentException;
use BeReborn\Exception\InitException;
use BeReborn\Http\Context;
use BeReborn\Http\Request;
use BeReborn\Http\Response;
use BeReborn\Pool\ClientsPool;
use BeReborn\Pool\Connection;
use BeReborn\Pool\RedisClient;
use BeReborn\Route\Limits;
use BeReborn\Route\Router;
use BeReborn\Service\Http\Server;
use Exception;

/**
 * Class BaseApplication
 * @package BeReborn\Base
 */
abstract class BaseApplication extends Service
{

	/**
	 * @var string
	 */
	public $runtimePath = APP_PATH . '/runtime';

	public $envPath = APP_PATH . '/.env';

	public $isCli = true;

	public $isCommand = false;
	public $isTask = false;

	/**
	 * Init constructor.
	 *
	 * @param array $config
	 *
	 * @throws
	 */
	public function __construct(array $config = [])
	{
		\BeReborn::$app = $this;

		$this->parseInt($config);

		$this->initErrorHandler($config);

		$this->enableEnvConfig();

		Component::__construct($config);
	}

	/**
	 * @return mixed
	 */
	public function enableEnvConfig()
	{
		if (!file_exists($this->envPath)) {
			return [];
		}
		$lines = $this->readLinesFromFile($this->envPath);
		foreach ($lines as $line) {
			if (!$this->isComment($line) && $this->looksLikeSetter($line)) {
				[$key, $value] = explode('=', $line);
				putenv(trim($key) . '=' . trim($value));
			}
		}
		return $lines;
	}


	/**
	 * Read lines from the file, auto detecting line endings.
	 *
	 * @param string $filePath
	 *
	 * @return array
	 */
	protected function readLinesFromFile($filePath)
	{
		// Read file into an array of lines with auto-detected line endings
		#$autodetect = ini_get('auto_detect_line_endings');
		#ini_set('auto_detect_line_endings', '1');
		$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
		#ini_set('auto_detect_line_endings', $autodetect);

		return $lines;
	}

	/**
	 * Determine if the line in the file is a comment, e.g. begins with a #.
	 *
	 * @param string $line
	 *
	 * @return bool
	 */
	protected function isComment($line)
	{
		$line = ltrim($line);

		return isset($line[0]) && $line[0] === '#';
	}

	/**
	 * Determine if the given line looks like it's setting a variable.
	 *
	 * @param string $line
	 *
	 * @return bool
	 */
	protected function looksLikeSetter($line)
	{
		return strpos($line, '=') !== false;
	}


	/**
	 * @param $config
	 *
	 * @throws
	 */
	public function parseInt(&$config)
	{
		if (isset($config['id'])) {
			$this->id = $config['id'];
			unset($config['id']);
		}
		if (isset($config['runtime'])) {
			$this->runtimePath = $config['runtime'];
			unset($config['runtime']);
		}
		if (!empty($this->runtimePath)) {
			if (!is_dir($this->runtimePath)) {
				mkdir($this->runtimePath, 777);
			}

			if (!is_dir($this->runtimePath) || !is_writeable($this->runtimePath)) {
				throw new InitException("Directory {$this->runtimePath} does not have write permission");
			}
		}
		$this->setComponents([
			'event'             => ['class' => Event::class],
			'request'           => ['class' => Request::class],
			'annotation'        => ['class' => Annotation::class],
			'limits'            => ['class' => Limits::class],
			'connections'       => ['class' => Connection::class],
			'redis_connections' => ['class' => RedisClient::class],
		]);
		if (isset($config['aliases'])) {
			$this->classAlias($config);
		}
		foreach ($this->moreComponents() as $key => $val) {
			if (isset($config['components'][$key])) {
				$config['components'][$key] = array_merge($val, $config['components'][$key]);
			} else {
				$config['components'][$key] = $val;
			}
		}
	}

	/**
	 * @param array $data
	 */
	private function classAlias(array &$data)
	{
		foreach ($data['aliases'] as $key => $val) {
			class_alias($val, $key, true);
		}
		unset($data['aliases']);
	}

	/**
	 * @param $config
	 *
	 * @throws Exception
	 */
	public function initErrorHandler(&$config)
	{
		if (isset($config['components']['error'])) {
			$this->set('error', $config['components']['error']);
			$this->get('error')->register();

			unset($config['components']['error']);
		}
	}

	/**
	 * @return Limits
	 * @throws ComponentException
	 */
	public function getLimits()
	{
		return $this->get('limits');
	}

	/**
	 * @return mixed
	 */
	public function getLocalIps()
	{
		return swoole_get_local_ip();
	}

	/**
	 * @return mixed
	 */
	public function getFirstLocal()
	{
		return current($this->getLocalIps());
	}

	/**
	 * @param $ip
	 * @return bool
	 */
	public function isLocal($ip)
	{
		return $this->getFirstLocal() == $ip;
	}

	/**
	 * @param $name
	 * @return mixed
	 */
	public function getSpecifyLocal($name)
	{
		$ips = $this->getLocalIps();
		foreach ($ips as $key => $ip) {
			if ($key == $name) {
				return $ip;
			}
		}
		return null;
	}

	/**
	 * @return Request|null
	 * @throws Exception
	 */
	public function getRequest()
	{
		return Context::getRequest();
	}

	/**
	 * @return Server
	 * @throws Exception
	 */
	public function getSocket()
	{
		return $this->get('socket');
	}

	/**
	 * @return Response
	 * @throws Exception
	 */
	public function getResponse()
	{
		return $this->get('response');
	}

	/**
	 * @return \Redis|Redis
	 * @throws Exception
	 */
	public function getRedis()
	{
		return $this->get('redis');
	}

	/**
	 * @return ErrorHandler
	 * @throws Exception
	 */
	public function getError()
	{
		return $this->get('error');
	}

	/**
	 * @return Event
	 * @throws ComponentException
	 */
	public function getEvent()
	{
		return $this->get('event');
	}

	/**
	 * @return ClientsPool
	 * @throws ComponentException
	 */
	public function getClientsPool(): ClientsPool
	{
		return $this->get('clientsPool');
	}

	/**
	 * @return Authorize
	 * @throws ComponentException
	 */
	public function getAuth()
	{
		return $this->get('auth');
	}

	/**
	 * @return Router
	 * @throws ComponentException
	 */
	public function getRouter(): Router
	{
		return $this->get('router');
	}

	/**
	 * @throws Exception
	 */
	private function moreComponents()
	{
		return [
			'error'       => ['class' => RestfulHandler::class],
			'redis'       => ['class' => Redis::class],
			'response'    => ['class' => Response::class],
			'clientsPool' => ['class' => ClientsPool::class],
			'auth'        => ['class' => Authorize::class],
			'router'      => ['class' => Router::class],
		];
	}
}
